#region

using System;
using System.Reflection;
using System.Text;
using Gurock.SmartInspect;
using Microsoft.Practices.Unity.InterceptionExtension;

#endregion

namespace SmartInspect.Extensions.Unity
{
    public class SiBaseHandler : ICallHandler
    {
        public bool LogArgumentsToViewer { private get; set; }
        public Level Level { private get; set; }
        public string SuffixSeparator { private get; set; }
        public string PrefixSeparator { private get; set; }
        public string NameSuffix { private get; set; }
        public string NamePrefix { private get; set; }
        public TypePolicy TypePolicy { private get; set; }

        #region ICallHandler Members

        public int Order { get; set; }

        public virtual IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            throw new NotImplementedException();
        }

        #endregion

        internal void LogArgs(string title, IMethodInvocation input, Session ses, LogEntryType entryType)
        {
            if(LogArgumentsToViewer)
            {
                InspectorViewerContext logContext = GetArgumentsViewerContext(input);
                ses.LogCustomContext(Level, title, entryType, logContext);
            }
        }

        private InspectorViewerContext GetArgumentsViewerContext(IMethodInvocation input)
        {
            var logContext = new InspectorViewerContext();
            logContext.StartGroup("");
            logContext.AppendKeyValue("Name", input.MethodBase.Name);
            logContext.AppendKeyValue("Type", GetTypeName(input.MethodBase.ReflectedType));
            logContext.StartGroup("Arguments");
            logContext.AppendKeyValue("Count", input.Arguments.Count);
            for(int index = 0; index < input.Arguments.Count; index++)
            {
                string paramName = input.Arguments.ParameterName(index);
                ParameterInfo info = input.Arguments.GetParameterInfo(paramName);

                if(info.IsOut)
                    logContext.AppendKeyValue("IsOut", info.IsOut);
                if(info.IsRetval)
                    logContext.AppendKeyValue("IsRetval", info.IsRetval);
                logContext.StartGroup(String.Format("Argument #{0}: {1}", (index + 1), paramName));
                logContext.AppendKeyValue("Type", GetTypeName(info.ParameterType));
                logContext.AppendKeyValue("Value", GetLogValue(input.Arguments[index]));
            }
            return logContext;
        }

        private static object GetLogValue(object value)
        {
            return value ?? "<NULL>";
        }

        internal void LogOutputs(string title, IMethodInvocation input, IMethodReturn ret, Session ses,
            LogEntryType entryType)
        {
            if(LogArgumentsToViewer)
            {
                InspectorViewerContext logContext = GetOutputsViewerContext(input, ret);
                ses.LogCustomContext(Level, title, entryType, logContext);
            }
        }

        private InspectorViewerContext GetOutputsViewerContext(IMethodInvocation input, IMethodReturn ret)
        {
            var logContext = new InspectorViewerContext();
            logContext.StartGroup("");
            logContext.AppendKeyValue("Name", input.MethodBase.Name);
            logContext.AppendKeyValue("Type", GetTypeName(input.MethodBase.ReflectedType));
            if(ret.ReturnValue != null)
            {
                logContext.StartGroup("Return value");
                logContext.AppendKeyValue("Type", GetTypeName(ret.ReturnValue.GetType()));
                logContext.AppendKeyValue("Value", ret.ReturnValue.ToString());
            }
            logContext.StartGroup("Arguments after method call");
            logContext.AppendKeyValue("Count", input.Arguments.Count);
            for(int index = 0; index < input.Arguments.Count; index++)
            {
                string paramName = input.Arguments.ParameterName(index);
                ParameterInfo info = input.Arguments.GetParameterInfo(paramName);

                logContext.StartGroup(String.Format("Argument #{0}: {1}", (index + 1), paramName));
                if(info.IsOut)
                    logContext.AppendKeyValue("IsOut", info.IsOut);
                if(info.IsRetval)
                    logContext.AppendKeyValue("IsRetval", info.IsRetval);
                logContext.AppendKeyValue("Type", GetTypeName(info.ParameterType));
                logContext.AppendKeyValue("Value", GetLogValue(input.Arguments[index]));
            }
            return logContext;
        }

        internal string GetTypeName(Type type)
        {
            return TypePolicy == TypePolicy.FullQualifiedName ? type.FullName : type.Name;
        }

        internal string FormatTitle(MethodBase method)
        {
            string baseTitle = String.Format("{0}.{1}", GetTypeName(method.DeclaringType), method.Name);
            string returnTitle = FormatTitle(baseTitle);
            return returnTitle;
        }

        internal string FormatTitle(string baseTitle)
        {
            if(string.IsNullOrEmpty(NamePrefix) && string.IsNullOrEmpty(NameSuffix))
                return baseTitle;
            var sb = new StringBuilder();
            if(!string.IsNullOrEmpty(NamePrefix))
                sb.Append(String.Format("{0}{1}", NamePrefix, PrefixSeparator));
            sb.Append(baseTitle);
            if(!string.IsNullOrEmpty(NameSuffix))
                sb.Append(String.Format("{0}{1}", SuffixSeparator, NameSuffix));
            return sb.ToString();
        }

        internal void StartCallHandlerInvoke(IMethodInvocation input, Session session)
        {
            if(SessionHelper.AttributeUsageStructureLogging)
            {
                session.EnterMethod(String.Format("ORDER:{3} -> {0}:{1}.{2}", GetType().Name,
                    GetTypeName(input.Target.GetType()), input.MethodBase.Name, Order));
            }
        }

        internal void StopCallHandlerInvoke(IMethodInvocation input, Session session)
        {
            if(SessionHelper.AttributeUsageStructureLogging)
            {
                session.LeaveMethod(String.Format("ORDER:{3} -> {0}:{1}.{2}", GetType().Name,
                    GetTypeName(input.Target.GetType()), input.MethodBase.Name, Order));
            }
        }
    }
}